﻿using System;
using AngryManagementMayhem.Controller.Map;
using AngryManagementMayhem.Controller.Player.Input;
using Transformable_Engine_v2.Engine.GameObjects.Containers;
using Transformable_Engine_v2.Engine.Input;
using Transformable_Engine_v2.Engine.Time;
using Transformable_Engine_v2.Engine.Transformable_Math.Geometry;

namespace AngryManagementMayhem.Controller.Player
{
    public abstract partial class Player : Container
    {
        public const float RunSpeed = 200;
        public const float DashSpeed = 500;
        public const int DashTime = 300;
        public const int FirstChargeTime = 500;
        public const int ChargeTime = 1000;
        public const float WallJumpSpeed = 1.05f;
        public static readonly TE_Vector2 JumpSpeed = new TE_Vector2(0, -4);
        public static readonly TE_Vector2 Gravity = new TE_Vector2(0, 10);
        public static readonly TE_Vector2 WallSlideGravity = new TE_Vector2(0, 2);

        public enum PlayerStateList
        {
            Idle,
            Jump,
            DoubleJump,
            Fall,
            Hurt,
            Dash,
            Run,
            WallCling,
        }

        private PlayerStateList _playerState;
        protected ImageContainer Body;
        //private ImageContainer _playerArm;
        protected ImageContainer ChargeParticle;
        private int _chargeLevel;
        private Timer _chargeTimer;

        private InputState _input;

        private TE_Rectangle _boundingBox;

        private TE_Vector2 _speed;

        private float _currentSpeed;

        private int _dashCount;
        private TE_Vector2 _dashDirection;
        private Timer _dashTimer;

        private int _jumpCount;

        private TE_Vector2 _oldPosition;

        private InputManager _inputKeys;

        public Player(TE_Vector2 position, InputManager input)
        {
            PositionGridBound = position;
            _oldPosition = Position;
            _inputKeys = input;
        }

        public override void LoadContent()
        {
            Body = new ImageContainer();
            ChargeParticle = new ImageContainer { Visible = true };

            GetChargeParticle(0);
            AddChildren(Body, ChargeParticle);
            ChangeState(PlayerStateList.Fall);

            _chargeLevel = 0;
            _chargeTimer = new Timer(ChargeTime, false, ChargeUp);

            _speed = new TE_Vector2();

            _jumpCount = 0;
            _dashCount = 0;
            _dashTimer = new Timer(DashTime, false, EndDash);

            _input = new InputState();
            KeyboardHandler.OnKeyboardPress += OnKeyboardInput;
            KeyboardHandler.OnKeyboardReleased += OnKeyboardInputReleased;
            GamepadHandler.OnButtonPressed += OnGamepadInput;
            GamepadHandler.OnButtonReleased += OnGamepadInputReleased;

            _boundingBox = new TE_Rectangle(0, 0, 4, 5, TE_Rectangle.Alignment.BottomMiddle).ToGridBound();

            base.LoadContent();
        }

        private void OnKeyboardInputReleased(KeyboardInput input)
        {
            OnInputReleased(input.KeyCode);
        }

        private void OnKeyboardInput(KeyboardInput input)
        {
            OnInput(input.KeyCode);
        }

        private void OnGamepadInputReleased(uint joystickid, KeyboardInput.Key buttonId)
        {
            if (joystickid == _inputKeys.GamepadId)
                OnInputReleased(buttonId);
        }

        private void OnGamepadInput(uint joystickid, KeyboardInput.Key buttonId)
        {
            if(joystickid == _inputKeys.GamepadId)
                OnInput(buttonId);
        }

        protected abstract void GetChargeParticle(int level);

        private void StartCharge()
        {
            if (!_input.IsCharging)
            {
                _input.IsCharging = true;
            
                _chargeTimer.Reset(FirstChargeTime);
                _chargeLevel = 0;
                GetChargeParticle(0);

                Shoot(0);
            }
        }

        private void Shoot(int chargeLevel)
        {
            ShootProjectile(chargeLevel, PositionGridBound, _input.LatestDirection * (_playerState == PlayerStateList.WallCling ? -1 : 1));
        }

        protected abstract void ShootProjectile(int chargeLevel, TE_Vector2 positionGridBound, TE_Vector2 direction);

        private void ChargeUp()
        {
            if (_chargeLevel < 3)
            {
                _chargeTimer.Reset(ChargeTime);
                _chargeLevel++;
                GetChargeParticle(_chargeLevel);
            }
        }

        private void ChargeEnd()
        {
            _input.IsCharging = false;

            _chargeTimer.Stop();
            GetChargeParticle(0);

            if(_chargeLevel > 0)
                Shoot(_chargeLevel);
        }

        private void EndDash()
        {
            if(_playerState == PlayerStateList.Dash) 
            {
                _dashTimer.Stop();
                ChangeState(PlayerStateList.Idle);
            }
        }

        void ChangeBody()
        {
            GetBody(_playerState);
        }

        protected abstract void GetBody(PlayerStateList playerState);

        void Move(TE_Vector2 delta)
        {
            _oldPosition = new TE_Vector2(Position);
            Position += delta;
        }

        private void OnInputReleased(KeyboardInput.Key input)
        {
            if (input == _inputKeys.Up)
                _input.Up = false;
            else if(input == _inputKeys.Down)
                _input.Down = false;
            else if(input == _inputKeys.Left)
                _input.Left = false;
            else if(input == _inputKeys.Right)
                _input.Right = false;
            else if(input == _inputKeys.R2)
                _input.IsDashing = false;
            else if(input == _inputKeys.West)
                ChargeEnd();
        }

        private void OnInput(KeyboardInput.Key input)
        {
            if (input == _inputKeys.Up)
                _input.Up = true;
            else if (input == _inputKeys.Down)
                _input.Down = true;
            else if (input == _inputKeys.Left)
                _input.Left = true;
            else if (input == _inputKeys.Right)
                _input.Right = true;
            else if (input == _inputKeys.R2)
                Dash();
            else if (input == _inputKeys.West)
                StartCharge();
            else if (input == _inputKeys.South)
                Jump();
        }

        private void Dash()
        {
            _dashDirection = _input.GetDirection();
            if (_input.IsDashing || _dashCount >= 1 || _dashDirection.IsZero())
                return;
            
            switch(_playerState) {
                case PlayerStateList.Run:
                    _input.IsDashing = true;

                    ChangeState(PlayerStateList.Dash);
                    _dashCount++;

                    ResetGravity();
                
                    _dashTimer.Reset();

                    if(_jumpCount < 1)
                        _jumpCount = 1;
                    break;
            }
        }

        void ChangeState(PlayerStateList state)
        {
            _playerState = state;
            ChangeBody();
            switch (_playerState)
            {
                case PlayerStateList.Idle:
                    if(_dashTimer.IsStarted)
                        ChangeState(PlayerStateList.Dash);
                    else
                    {
                        ResetGravity();
                        _jumpCount = 0;
                        _currentSpeed = RunSpeed;
                        _dashCount = 0;
                    }
                    break;
                case PlayerStateList.Fall:
                case PlayerStateList.Jump:
                    if(_jumpCount < 1)
                        _jumpCount = 1;
                    break;
                case PlayerStateList.DoubleJump:
                    break;
                case PlayerStateList.Hurt:
                    _currentSpeed = RunSpeed;
                    break;
                case PlayerStateList.Dash:
                    _currentSpeed = DashSpeed;
                    break;
                case PlayerStateList.Run:
                    break;
                case PlayerStateList.WallCling:
                    ResetGravity();
                    _jumpCount = 2;
                    _currentSpeed = RunSpeed;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("state");
            }
            
        }

        public override void FixedStepUpdate(System.TimeSpan gameTime)
        {
            switch(_playerState) 
            {
                case PlayerStateList.Idle:
                    IdleUpdate(gameTime);
                    break;
                case PlayerStateList.Run:
                    RunUpdate(gameTime);
                    break;
                case PlayerStateList.Jump:
                case PlayerStateList.DoubleJump:
                case PlayerStateList.Fall:
                    JumpUpdate(gameTime);
                    break;
                case PlayerStateList.WallCling:
                    WallClingUpdate(gameTime);
                    break;
                case PlayerStateList.Dash:
                    DashUpdate(gameTime);
                    break;
            }

            base.FixedStepUpdate(gameTime);
        }

        void IdleUpdate(System.TimeSpan gameTime)
        {
            if(ApplyRunSpeed(gameTime))
                ChangeState(PlayerStateList.Run);

            ApplyCollision();
        }

        void RunUpdate(System.TimeSpan gameTime)
        {
            if(!ApplyRunSpeed(gameTime))
                ChangeState(PlayerStateList.Idle);

            ApplyGravity(gameTime);

            var collisionState = ApplyCollision();

            if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.None))
                ChangeState(PlayerStateList.Fall);
            else if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Right) ||
                CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Left)) 
                ChangeState(PlayerStateList.WallCling);
            
            else if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Floor))
                ResetGravity();
            
        }

        void DashUpdate(System.TimeSpan gameTime)
        {
            Move(_dashDirection * (DashSpeed * gameTime.TotalSeconds));

            ApplyGravity(gameTime);

            var collisionState = ApplyCollision();

            if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Right) ||
                CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Left)) 
            {
                EndDash();
            }
            else if (CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.None))
            {
                ChangeState(PlayerStateList.Fall);
            }
        }

        void JumpUpdate(System.TimeSpan gameTime)
        {
            ApplyRunSpeed(gameTime);

            ApplyGravity(gameTime);

            var collisionState = ApplyCollision();

            if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Floor))
                ChangeState(PlayerStateList.Idle);
            else if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Right) ||
                CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Left))
                ChangeState(PlayerStateList.WallCling);
            else if(_speed.Y > 0)
                ChangeState(PlayerStateList.Fall);
        }

        void WallClingUpdate(System.TimeSpan gameTime)
        {
            ApplyRunSpeed(gameTime);

            ApplyWallSlideGravity(gameTime);

            var collisionState = ApplyCollision();

            if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Floor))
            {
                if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Left) ||
                   CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.Right))
                    ResetGravity();
                else 
                    ChangeState(PlayerStateList.Idle);
            }
                
            if(CollisionDefinition.ContainsType(collisionState, CollisionDefinition.CollisionTypeList.None))
                ChangeState(PlayerStateList.DoubleJump);
        }

        bool ApplyRunSpeed(System.TimeSpan gameTime)
        {
            if(_input.Left) {
                Move(new TE_Vector2((float) (-_currentSpeed * gameTime.TotalSeconds), 0));
                _input.LatestDirection = new TE_Vector2(-1, 0);
                return true;
            }
            if(_input.Right) {
                Move(new TE_Vector2((float) (_currentSpeed * gameTime.TotalSeconds), 0));
                _input.LatestDirection = new TE_Vector2(1, 0);
                return true;
            }

            _input.GetDirection();

            return false;
        }

        void ApplyGravity(System.TimeSpan gameTime)
        {
            Move(_speed);
            _speed.AddOffset(Gravity * gameTime.TotalSeconds);

            if(Math.Abs(_speed.X) > 0) {
                _speed.X *= (float) (1 - 3 * gameTime.TotalSeconds);

                //If the player is going on the other side, cancel the wall jump
                if((_input.GetDirection().X / _speed.X) >= 0)
                    _speed.X = 0;

                else if(Math.Abs(_speed.X) < 0.25)
                _speed.X = 0;
            }
                
            
        }
        void ResetGravity()
        {
            _speed = new TE_Vector2();
        }

        void ApplyWallSlideGravity(System.TimeSpan gameTime)
        {
            Move(_speed);
            _speed += WallSlideGravity*gameTime.TotalSeconds;
        }

        void Jump()
        {
            //Special wall jump
            if(_playerState == PlayerStateList.WallCling) {
                _speed = JumpSpeed - _input.GetDirection()*WallJumpSpeed;
                ChangeState(PlayerStateList.DoubleJump);
            }
            //Normal jump
            else if (_jumpCount < 2)
            {
                _speed = new TE_Vector2(JumpSpeed);
                _jumpCount++;

                if(_jumpCount == 1)
                    ChangeState(PlayerStateList.Jump);
                else 
                    ChangeState(PlayerStateList.DoubleJump);
                
            }
        }

        
    }
}